#ifndef CODUO_CM_TRACE_CORE_PRIVATE_H
#define CODUO_CM_TRACE_CORE_PRIVATE_H

#include <stddef.h>
#include <stdint.h>

#include "../core_memory/core_memory_private.h"
#include "coduo_engine_structs.h"

/* CoD extends Quake III's traceWork_t, but the complete 0x118-byte i386
 * layout still agrees field-for-field between the Windows and Linux engines. */
typedef struct traceWork_s {
    vec3_t start;
    vec3_t end;
    vec3_t delta;
    float deltaLengthSquared;
    vec3_t mins;
    vec3_t maxs;
    vec3_t offsets[8];
    /* Stock CM_Trace (0x805ac11) and CM_SightTrace (0x805cf96) store
     * maxs[0]+maxs[1]+maxs[2] here (one float rounding) right after the
     * sphere setup; no reader exists in the binary. */
    float maxsSum;
    vec3_t bounds[2];
    int32_t contents;
    qboolean isPoint;
    trace_t trace;
    sphere_t sphere;
    vec3_t sphereExtents;
} traceWork_t;

/* Shared CoD wrapper used by both transformed trace implementations. */
typedef struct cmTraceSphereRecord_s {
    sphere_t sphere;
    vec3_t extents;
} cmTraceSphereRecord_t;

#if UINTPTR_MAX == UINT32_MAX
_Static_assert(offsetof(traceWork_t, start) == 0x00,
               "traceWork_t.start mismatch");
_Static_assert(offsetof(traceWork_t, end) == 0x0c,
               "traceWork_t.end mismatch");
_Static_assert(offsetof(traceWork_t, delta) == 0x18,
               "traceWork_t.delta mismatch");
_Static_assert(offsetof(traceWork_t, deltaLengthSquared) == 0x24,
               "traceWork_t.deltaLengthSquared mismatch");
_Static_assert(offsetof(traceWork_t, mins) == 0x28,
               "traceWork_t.mins mismatch");
_Static_assert(offsetof(traceWork_t, maxs) == 0x34,
               "traceWork_t.maxs mismatch");
_Static_assert(offsetof(traceWork_t, offsets) == 0x40,
               "traceWork_t.offsets mismatch");
_Static_assert(offsetof(traceWork_t, bounds) == 0xa4,
               "traceWork_t.bounds mismatch");
_Static_assert(offsetof(traceWork_t, contents) == 0xbc,
               "traceWork_t.contents mismatch");
_Static_assert(offsetof(traceWork_t, isPoint) == 0xc0,
               "traceWork_t.isPoint mismatch");
_Static_assert(offsetof(traceWork_t, trace) == 0xc4,
               "traceWork_t.trace mismatch");
_Static_assert(offsetof(traceWork_t, trace.normal) == 0xd4,
               "traceWork_t.trace.normal mismatch");
_Static_assert(offsetof(traceWork_t, trace.allsolid) == 0xf2,
               "traceWork_t.trace.allsolid mismatch");
_Static_assert(offsetof(traceWork_t, trace.startsolid) == 0xf3,
               "traceWork_t.trace.startsolid mismatch");
_Static_assert(offsetof(traceWork_t, sphere) == 0xf4,
               "traceWork_t.sphere mismatch");
_Static_assert(offsetof(traceWork_t, sphere.radius) == 0xf8,
               "traceWork_t.sphere.radius mismatch");
_Static_assert(offsetof(traceWork_t, sphere.halfheight) == 0xfc,
               "traceWork_t.sphere.halfheight mismatch");
_Static_assert(offsetof(traceWork_t, sphere.offset) == 0x100,
               "traceWork_t.sphere.offset mismatch");
_Static_assert(offsetof(traceWork_t, sphereExtents) == 0x10c,
               "traceWork_t.sphereExtents mismatch");
_Static_assert(sizeof(traceWork_t) == 0x118,
               "traceWork_t size mismatch");
_Static_assert(offsetof(cmTraceSphereRecord_t, sphere) == 0x00,
               "cmTraceSphereRecord_t.sphere mismatch");
_Static_assert(offsetof(cmTraceSphereRecord_t, extents) == 0x18,
               "cmTraceSphereRecord_t.extents mismatch");
_Static_assert(sizeof(cmTraceSphereRecord_t) == 0x24,
               "cmTraceSphereRecord_t size mismatch");
#endif

extern int32_t *cm_leafbrushes;
extern int32_t *cm_leafsurfaces;
extern coduo_collision_brush_t *cm_brushes;
extern coduo_collision_model_t *cm_cmodels;
extern coduo_collision_terrain_patch_t *cm_terrainPatches;
extern dshader_t *cm_materials;
extern int32_t cm_numBrushes;
extern int32_t cm_numLeafBrushes;
extern int32_t cm_numSubModels;
extern int32_t cm_numTerrainPatches;
/* ORIGINAL_BINARY_BUG: the dword duplicate-suppression generation wraps
 * without clearing saved brush/terrain generations; see
 * original-bugs/coduo-collision-checkcount-generation-wrap.md. */
extern int32_t cm_checkcount;
extern cvar_t *cm_noCurves;
extern coduo_collision_model_t cm_boxModel;
extern coduo_collision_brush_t *cm_boxBrush;

const coduo_collision_model_t *CM_ClipHandleToModel(int32_t handle);
int32_t CM_TempBoxModelContents(void);
void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]);
void RotatePoint(vec3_t point, vec3_t matrix[3]);
void TransposeMatrix(vec3_t in[3], vec3_t out[3]);
float VectorNormalize2(const vec3_t in, vec3_t out);

int32_t CM_TempBoxModel(const vec3_t mins, const vec3_t maxs,
                        int32_t contents,
                        qboolean capsule);

void CM_TestBoxInBrush(traceWork_t *traceWork,
                       const coduo_collision_brush_t *brush);
void CM_TestInLeaf(traceWork_t *traceWork,
                   const coduo_collision_leaf_t *leaf);
void CM_TestCapsuleInCapsule(traceWork_t *traceWork);
void CM_TestBoundingBoxInCapsule(traceWork_t *traceWork);
void CM_PositionTest(traceWork_t *traceWork);
void CM_InitBoxHull(void);
qboolean CM_TraceMayIntersectBox(
    const traceWork_t *traceWork,
    const vec3_t mins,
    const vec3_t maxs);
void CM_TraceThroughPatch(
    traceWork_t *traceWork,
    const coduo_collision_terrain_patch_t *terrainPatch);
void CM_TraceThroughBrush(traceWork_t *traceWork,
                          const coduo_collision_brush_t *brush);
void CM_TraceThroughLeaf(traceWork_t *traceWork,
                         const coduo_collision_leaf_t *leaf);
qboolean CM_TraceSphereThroughSphere(traceWork_t *traceWork,
                                     const vec3_t start,
                                     const vec3_t end,
                                     const vec3_t sphereOrigin,
                                     float sphereRadius);
qboolean CM_TraceCylinderThroughCylinder(traceWork_t *traceWork,
                                         const vec3_t cylinderOrigin,
                                         float cylinderHalfHeight,
                                         float cylinderRadius);
void CM_TraceCapsuleThroughCapsule(traceWork_t *traceWork);
void CM_TraceBoundingBoxThroughCapsule(traceWork_t *traceWork);
void CM_TraceThroughTree(traceWork_t *traceWork,
                         int32_t nodeNum,
                         float startFraction,
                         float endFraction,
                         const vec3_t start,
                         const vec3_t end);
void CM_Trace(trace_t *trace,
              const vec3_t start,
              const vec3_t end,
              const vec3_t mins,
              const vec3_t maxs,
              int32_t model,
              int32_t brushMask,
              qboolean capsule,
              const cmTraceSphereRecord_t *sphere);
void CM_BoxTrace(trace_t *trace,
                 const vec3_t start,
                 const vec3_t end,
                 const vec3_t mins,
                 const vec3_t maxs,
                 int32_t model,
                 int32_t brushMask,
                 qboolean capsule);
void CM_TransformedBoxTrace(trace_t *trace,
                            const vec3_t start,
                            const vec3_t end,
                            const vec3_t mins,
                            const vec3_t maxs,
                            int32_t model,
                            int32_t brushMask,
                            const vec3_t origin,
                            const vec3_t angles,
                            qboolean capsule);
void CM_TransformedBoxTraceExternal(trace_t *trace,
                                    const vec3_t start,
                                    const vec3_t end,
                                    const vec3_t mins,
                                    const vec3_t maxs,
                                    int32_t model,
                                    int32_t brushMask,
                                    const vec3_t origin,
                                    const vec3_t angles,
                                    qboolean capsule);
qboolean CM_SightTraceThroughPatch(
    const traceWork_t *traceWork,
    const coduo_collision_terrain_patch_t *terrainPatch);
int32_t
CM_SightTraceThroughBrush(const traceWork_t *traceWork,
                          const coduo_collision_brush_t *brush);
int32_t
CM_SightTraceThroughLeaf(const traceWork_t *traceWork,
                         const coduo_collision_leaf_t *leaf);
qboolean CM_SightTraceSphereThroughSphere(
    const traceWork_t *traceWork,
    const vec3_t start,
    const vec3_t end,
    const vec3_t sphereOrigin,
    float sphereRadius);
qboolean CM_SightTraceCylinderThroughCylinder(
    const traceWork_t *traceWork,
    const vec3_t capsuleCenter,
    float capsuleHalfHeight,
    float capsuleRadius);
int32_t
CM_SightTraceCapsuleThroughCapsule(const traceWork_t *traceWork);
int32_t
CM_SightTraceBoundingBoxThroughCapsule(traceWork_t *traceWork);
int32_t
CM_SightTraceThroughTree(const traceWork_t *traceWork,
                         int32_t nodeNum,
                         float startFraction,
                         float endFraction,
                         const vec3_t start,
                         const vec3_t end);
int32_t
CM_SightTrace(int32_t result,
              const vec3_t start,
              const vec3_t end,
              const vec3_t mins,
              const vec3_t maxs,
              int32_t model,
              const vec3_t origin,
              int32_t brushMask,
              qboolean capsule,
              const cmTraceSphereRecord_t *sphere);
int32_t
CM_BoxSightTrace(int32_t result,
                 const vec3_t start,
                 const vec3_t end,
                 const vec3_t mins,
                 const vec3_t maxs,
                 int32_t model,
                 int32_t brushMask,
                 qboolean capsule);
int32_t CM_TransformedBoxSightTrace(
    int32_t result,
    const vec3_t start,
    const vec3_t end,
    const vec3_t mins,
    const vec3_t maxs,
    int32_t model,
    int32_t brushMask,
    const vec3_t origin,
    const vec3_t angles,
    qboolean capsule);
float CM_TraceFloatAbs(float value);

#endif
